package com.test.learnopenglandroid;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.HashMap;
import java.util.Map;

public final class OpenGLHelper {
	static public String TAG = "OpenGLHelper";

    static public int createProgram(String vertexSource, String fragmentSource) {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) {
            return -1;
        }
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (fragmentShader == 0) {
            return -2;
        }

        int program = GLES20.glCreateProgram();
        if (program != 0) {
            GLES20.glAttachShader(program, vertexShader);
            if( GLES20.glGetError() != GLES20.GL_NO_ERROR )
                return -3;
            GLES20.glAttachShader(program, fragmentShader);
            if( GLES20.glGetError() != GLES20.GL_NO_ERROR )
                return -4;
            GLES20.glLinkProgram(program);
            int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] != GLES20.GL_TRUE) {
                Log.e(TAG, "Could not link program: ");
                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program = -5;
            }else{
                Log.i(TAG,"link shader program ok");
                GLES20.glDeleteShader(vertexShader);
				GLES20.glDeleteShader(fragmentShader);
            }
        }
        return program;
    }
	
	static public void deleteProgram( int[] p){

		if( p != null && p[0] != 0 ){
			GLES20.glDetachShader( p[0], p[1] );
			GLES20.glDeleteShader(p[1]);
			GLES20.glDetachShader( p[0], p[2] );
			GLES20.glDeleteShader(p[2]);
			GLES20.glDeleteProgram(p[0]);
		}
	}
	static public int loadShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            GLES20.glShaderSource(shader, source);
            GLES20.glCompileShader(shader);
            int[] compiled = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0) {
                Log.e(TAG, "Could not compile shader " + getShaderTypeStr(shaderType) + ":");
                Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }

    static String getShaderTypeStr(int type) {
        switch (type) {
            case GLES20.GL_VERTEX_SHADER:
                return "GL_VERTEX_SHADER";
            case GLES20.GL_FRAGMENT_SHADER:
                return "GL_FRAGMENT_SHADER";
            default:
                return "unknow";
        }
    }
	
	static public int loadTextureFromDrawable(Context context, int drawableid){
		int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

        Bitmap bitmap;
        try {
        	bitmap = BitmapFactory.decodeResource(context.getResources(), drawableid);
        } finally {
        }

        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
        return textures[0];
	}
	static public int loadTextureFromBitmap( Bitmap bitmap){
		int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
        return textures[0];
	}

    static public int loadTextureFromRawRes( Context context, int rawResId){
        InputStream in = context.getResources().openRawResource(rawResId);
        Bitmap bitmap = BitmapFactory.decodeStream(in);

        int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
        return textures[0];
    }
	
	static public int genTexture(){
		int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        
        return textures[0];
	}
	static public String readScriptFromRawRes(Context context, int id){
        String result = "";
            try {   
                InputStream in = context.getResources().openRawResource(id);
                InputStreamReader inputreader = new InputStreamReader(in);
                BufferedReader buffreader = new BufferedReader(inputreader);
                String line;
                while (( line = buffreader.readLine()) != null) {
                    result += line + "\n";
                }                
            } catch (Exception e) {
                e.printStackTrace();   
            }   
            return result;   
    }

    static public int createShaderProgramFromRawRes(Context context,int vsRawId,int fsRawId){
        String vertexShaderSource = OpenGLHelper.readScriptFromRawRes(context, vsRawId);
        String fragmentShaderSource = OpenGLHelper.readScriptFromRawRes(context, fsRawId);
        int lightingShaderProgram = OpenGLHelper.createProgram(vertexShaderSource, fragmentShaderSource);
        if (lightingShaderProgram < 0) {
            throw new RuntimeException("Could not createProgram");
        }
        return lightingShaderProgram;
    }

	static public int loadTextureFromFile( String name ){
        Bitmap bitmap;
        try {
            bitmap = BitmapFactory.decodeFile(name);
            int[] textures = new int[1];
            GLES20.glGenTextures(1, textures, 0);

            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
            bitmap.recycle();
            return textures[0];
        }catch(Exception e){
        }
        return -1;
	}

    static public void setAttributeAndEnable(int attrHandle, FloatBuffer buffer, int count, int stride ){
        buffer.position(0);
        GLES20.glVertexAttribPointer(attrHandle,count, GLES20.GL_FLOAT, false, stride, buffer);
        GLES20.glEnableVertexAttribArray(attrHandle);
    }
    static public void setAttributeAndEnable(int attrHandle, int vbo, int count, int stride ){
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
        GLES20.glVertexAttribPointer(attrHandle,count, GLES20.GL_FLOAT, false, stride, 0);
        GLES20.glEnableVertexAttribArray(attrHandle);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
    }
    /**
     * 将vbo的数据写入指定attrHandle
     * @count 一个Attribute的vec长度，例如顶点是3,贴图纹理坐标是2
     * @stride 一次读取数据的步长，注意单位是字节
     * @offset 数据在一次读取中的偏移，注意单位是字节，例如偏移3个float数据，应该是偏移3*4
     * 注意：运行后vbo绑定为0,即恢复到没有绑定状态，传入的Attribute已经打开
     * */
    static public void setAttributeAndEnable(int attrHandle, int vbo, int count, int stride , int offset){
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
        GLES20.glVertexAttribPointer(attrHandle,count, GLES20.GL_FLOAT, false, stride, offset);
        GLES20.glEnableVertexAttribArray(attrHandle);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
    }

    static public int setAttributeAndEnable(int programHandler,String attributeName, int vboHandler, int count, int stride , int offset){
        int attrHandle = GLES20.glGetAttribLocation(programHandler, attributeName);
        if (attrHandle == -1) {
            throw new RuntimeException("Could not glGetAttribLocationn for "+attributeName);
        }
        OpenGLHelper.setAttributeAndEnable(attrHandle,vboHandler,count,stride,offset);
        return attrHandle;
    }

    static public void disableAttribute(int attrHandle){
        GLES20.glDisableVertexAttribArray(attrHandle);
    }

    static public int setUniform(int program,String uniformName ,int value){
        int uniform = GLES20.glGetUniformLocation(program,uniformName);
        if(uniform<0){
            throw new RuntimeException("no such uniform "+uniformName);
        }
        GLES20.glUniform1i(uniform,value);
        return  uniform;
    }

    static public int setUniform(int program,String uniformName ,float value){
        int uniform = GLES20.glGetUniformLocation(program,uniformName);
        if(uniform<0){
            throw new RuntimeException("no such uniform "+uniformName);
        }
        GLES20.glUniform1f(uniform,value);
        return  uniform;
    }

    static public int setUniform3fv(int program,String uniformName ,float[] value){
        int uniform = GLES20.glGetUniformLocation(program,uniformName);
        if(uniform<0){
            throw new RuntimeException("no such uniform "+uniformName);
        }
//      第二个参数指明要更改的元素个数。如果目标uniform变量不是一个数组，那么这个值应该设为1；如果是数组，则应该设置为>=1。
        GLES20.glUniform3fv(uniform,1,value,0);
        return  uniform;
    }

    static public int setUniformMatrix4fv(int program,String uniformName ,float[] matrix ){
        int uniform = GLES20.glGetUniformLocation(program,uniformName);
        if(uniform<0){
            throw new RuntimeException("no such uniform "+uniformName);
        }
        GLES20.glUniformMatrix4fv(uniform, 1, false, matrix, 0);
        return  uniform;
    }
    /**
     * 生成一个VBO，将输入的FloatBuffer数据放入
     * @return VBO的句柄。注意运行完以后并不会绑定当前VBO
     * */
    static public int genVBOArrayBuffer( FloatBuffer vertex){
        int[] vbo = new int[1];
        GLES20.glGenBuffers(1, vbo, 0 );
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[0] );
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertex.capacity()*4, vertex, GLES20.GL_STATIC_DRAW );
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0 );
        return vbo[0];
    }

    static public int genVBOArrayBuffer( float[] data){
        FloatBuffer verticesBuffer = ByteBuffer.allocateDirect(data.length
                * Const.FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
        verticesBuffer.put(data);
        verticesBuffer.position(0);

        int[] vbo = new int[1];
        GLES20.glGenBuffers(1, vbo, 0 );
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[0] );
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, verticesBuffer.capacity()*Const.FLOAT_SIZE_BYTES, verticesBuffer, GLES20.GL_STATIC_DRAW );
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0 );
        return vbo[0];
    }


    static public int genElementBuffer( ShortBuffer inb){
        int[] vbo = new int[1];
        GLES20.glGenBuffers(1, vbo, 0 );
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, vbo[0] );
        GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, inb.capacity()*2, inb, GLES20.GL_STATIC_DRAW );
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0 );
        return vbo[0];
    }

    static public void drawElement(ShortBuffer index){
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, index.capacity() , GLES20.GL_UNSIGNED_SHORT, index);
    }

    static public void bindEboAndDrawElement(int ebo, int count){
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, ebo);
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, count , GLES20.GL_UNSIGNED_SHORT, 0);
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0 );
    }
}
